#include <cppunit/extensions/HelperMacros.h>

#include <iostream>
#include <cstdio>
#include <cstring>

#include "wrappers.h"
#include "execute.h"

class CheckForOtherTest: public CPPUNIT_NS::TestFixture
{
  void setFRead(std::string fread_ret_stub);

public:

  void testInvalidCalls();
  void testCmd();
  void testArgs();
  void testSelectProcDirs();

  CPPUNIT_TEST_SUITE( CheckForOtherTest );
  CPPUNIT_TEST( testInvalidCalls );
  CPPUNIT_TEST( testCmd );
  CPPUNIT_TEST( testArgs );
  CPPUNIT_TEST( testSelectProcDirs );
  CPPUNIT_TEST_SUITE_END();
};

CPPUNIT_TEST_SUITE_REGISTRATION( CheckForOtherTest );

void CheckForOtherTest::setFRead(std::string fread_ret_stub) {
  // Helper for fread as it's constantly being altered
  g_fread_mock = [fread_ret_stub](void *buffer, size_t size, size_t count, FILE *stream) -> size_t {
    strncpy((char*)buffer, fread_ret_stub.c_str(), count);
    return std::min(fread_ret_stub.size(), count);
  };
}

void CheckForOtherTest::testInvalidCalls() {
  char *args[] = {
    (char*)"/opt/szarp/bin/parcook",
    0
  };

  g_scandir_mock = [](const char * path, struct dirent ***, int (*)(const struct dirent *), int (*)(const struct dirent **, const struct dirent **)) -> int {
    return -1;
  };

  CPPUNIT_ASSERT_EQUAL(-1, check_for_other(sizeof(args)/sizeof(char*) - 1, args));

  // Return a dir entry that has a cmd name that's extremely long
  g_scandir_mock = [](const char * path, struct dirent ***namelist, int (*)(const struct dirent *), int (*)(const struct dirent **, const struct dirent **)) -> int {
    struct dirent **v = (struct dirent **) malloc(sizeof(struct dirent *));
    struct dirent *de = (struct dirent *) malloc(sizeof(struct dirent));
    std::string fname = "323140349539457814827463572937561976392736976139743231403495394578148274635729375619763927369761397432314034953945781482746357293756197639273697613974323140349539457814827463572937561976392736976139743231403495394578148274635729375619763927369761397432"; // less than 256

    strcpy(de->d_name, fname.c_str());
    v[0] = de;
    (*namelist) = v;
    return 1;
  };

  CPPUNIT_ASSERT_EQUAL(0, check_for_other(sizeof(args)/sizeof(char*) - 1, args));

  char *long_args[] = {
    (char*)"/opt/szarp/bin/parcook",
    (char*)"--very_long_arg1=very_long_value_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    0
  };

  CPPUNIT_ASSERT_EQUAL(0, check_for_other(sizeof(long_args)/sizeof(char*) - 1, long_args));
}

void CheckForOtherTest::testCmd() {
  char *args[] = {
    (char*)"/opt/szarp/bin/parcook",
    0
  };

  // Return a dir entry that has a different cmd name
  g_scandir_mock = [](const char * path, struct dirent ***namelist, int (*)(const struct dirent *), int (*)(const struct dirent **, const struct dirent **)) -> int {
    struct dirent **v = (struct dirent **) malloc(sizeof(struct dirent *));
    struct dirent *de = (struct dirent *) malloc(sizeof(struct dirent));
    std::string fname = "3231";
    strcpy(de->d_name, fname.c_str());
    v[0] = de;
    (*namelist) = v;
    return 1;
  };

  // Assume fopen doesnt fail (check it separately)
  g_fopen_mock = [](const char *, const char *) -> FILE* { return (FILE*) 1; };

  // When fn tries to read the cmdline file, return error
  g_fread_mock = [](void *, size_t, size_t, FILE *) -> size_t {
    return -1;
  };

  CPPUNIT_ASSERT_EQUAL(0, check_for_other(sizeof(args)/sizeof(char*) - 1, args));

  this->setFRead("/opt/szarp/bin/othercmd");
  CPPUNIT_ASSERT_EQUAL(0, check_for_other(sizeof(args)/sizeof(char*) - 1, args));

  this->setFRead("/opt/szarp/bin/parcook");
  CPPUNIT_ASSERT_EQUAL(3231, check_for_other(sizeof(args)/sizeof(char*) - 1, args));

  this->setFRead("./parcook");
  CPPUNIT_ASSERT_EQUAL(3231, check_for_other(sizeof(args)/sizeof(char*) - 1, args));

  this->setFRead("/opt/szarp/bin/parcook --somearg");
  CPPUNIT_ASSERT_EQUAL(0, check_for_other(sizeof(args)/sizeof(char*) - 1, args));
}

void CheckForOtherTest::testArgs() {
  char *args[] = {
    (char*)"/opt/szarp/bin/parcook",
    (char*)"--somearg",
    (char*)"--someotherarg=value",
    0
  };

  // Return a dir entry that has a different cmd name
  g_scandir_mock = [](const char * path, struct dirent ***namelist, int (*)(const struct dirent *), int (*)(const struct dirent **, const struct dirent **)) -> int {
    struct dirent **v = (struct dirent **) malloc(sizeof(struct dirent *));
    struct dirent *de = (struct dirent *) malloc(sizeof(struct dirent));
    std::string fname = "3231";
    strcpy(de->d_name, fname.c_str());
    v[0] = de;
    (*namelist) = v;
    return 1;
  };

  // Assume fopen doesnt fail (check it separately)
  g_fopen_mock = [](const char *, const char *) -> FILE* { return (FILE*) 1; };

  this->setFRead("/opt/szarp/bin/parcook");
  CPPUNIT_ASSERT_EQUAL(0, check_for_other(sizeof(args)/sizeof(char*) - 1, args));

  this->setFRead("/opt/szarp/bin/parcook --somearg --someotherarg=othervalue");
  CPPUNIT_ASSERT_EQUAL(0, check_for_other(sizeof(args)/sizeof(char*) - 1, args));

  this->setFRead("/opt/szarp/bin/parcook --somearg --someotherarg=value");
  CPPUNIT_ASSERT_EQUAL(3231, check_for_other(sizeof(args)/sizeof(char*) - 1, args));

  char *long_args[] = {
    (char*)"/opt/szarp/bin/parcook",
    (char*)"--very_long_arg1=very_long_value_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    0
  };

  // When fn tries to read the cmdline file, return the same command
  this->setFRead("/opt/szarp/bin/parcook --very_long_arg1=very_long_value_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
  CPPUNIT_ASSERT_EQUAL(3231, check_for_other(sizeof(long_args)/sizeof(char*) - 1, long_args));

  // When fn tries to read the cmdline file, return the same command but in different dir
  this->setFRead("/otherdir/parcook --very_long_arg1=very_long_value_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
  // TODO: should match
  CPPUNIT_ASSERT_EQUAL(0, check_for_other(sizeof(long_args)/sizeof(char*) - 1, long_args));

  // When fn tries to read the cmdline file, return the same command but with arguments that don't fit in 256 characters
  this->setFRead("/opt/szarp/bin/parcook --very_long_arg1=very_long_value_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabb");

  // TODO: should not match (dynamic array sizes)
  CPPUNIT_ASSERT_EQUAL(3231, check_for_other(sizeof(long_args)/sizeof(char*) - 1, long_args));
}

void CheckForOtherTest::testSelectProcDirs() {
  // Check that matches numeric
  CPPUNIT_ASSERT_EQUAL(0, select_proc_dirs("2313", "2313"));

  // Check that does not match alphanumeric
  CPPUNIT_ASSERT_EQUAL(0, select_proc_dirs("231a", "231a"));
  CPPUNIT_ASSERT_EQUAL(0, select_proc_dirs("231b", "231a"));

  // Check that does not match non-matching numeric
  CPPUNIT_ASSERT_EQUAL(1, select_proc_dirs("2313", "2314"));
}
